﻿#region Copyright Notice
// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;

namespace AbstractClass.Extensions
{
    /// <summary>
    /// Contains extension methods for <see cref = "string"/>.
    /// </summary>
    public static class StringExtensions
    {
        /// <summary>
        ///   Calculates the similarities between two strings.
        /// </summary>
        /// <param name="strA">The first string.</param>
        /// <param name="strB">The second string.</param>
        /// <returns>A <see cref="float"/> value that indicates the similarity between two strings,
        /// as shown in the following table.
        /// <list type="">
        /// <item>Zero : No similarity between <paramref name="strA"/> and <paramref name="strB"/>.</item>
        /// <item>One : <paramref name="strA"/> and <paramref name="strB"/> are similar.</item>
        /// <item>Between zero and one : <paramref name="strA"/> and <paramref name="strB"/> have some differences depending on case, special symbols etc.</item>
        /// </list>
        /// </returns>
        /// <remarks>
        ///   Here default values are:
        /// <para>
        /// ignoreCase = false, alphanumericScore = 1f, specialCharScore = 0.05f, caseScore = 0.5f
        /// </para>
        /// </remarks>
        public static float Similarity(this string strA, string strB)
        {
            return Similarity(strA, strB, false, 1f, 0.05f, 0.5f);
        }

        /// <summary>
        /// Calculates the similarities between two strings.
        /// </summary>
        /// <param name="strA">The first string.</param>
        /// <param name="strB">The second string.</param>
        /// <param name="ignoreCase">if set to <c>true</c> the comparison is case insensitive.</param>
        /// <returns>A <see cref="float"/> value that indicates the similarity between two strings,
        /// as shown in the following table.
        /// <list type="">
        /// <item>Zero : No similarity between <paramref name="strA"/> and <paramref name="strB"/>.</item>
        /// <item>One : <paramref name="strA"/> and <paramref name="strB"/> are similar.</item>
        /// <item>Between zero and one : <paramref name="strA"/> and <paramref name="strB"/> have some differences depending on case, special symbols etc.</item>
        /// </list>
        /// </returns>
        /// <remarks>
        ///   Here default values are:
        /// <para>
        /// alphanumericScore = 1f, specialCharScore = 0.05f, caseScore = 0.5f
        /// </para>
        /// </remarks>
        public static float Similarity(this string strA, string strB, bool ignoreCase)
        {
            return Similarity(strA, strB, ignoreCase, 1f, 0.05f, 0.5f);
        }

        /// <summary>
        ///   Calculates similarities between two strings.
        /// </summary>
        /// <param name="strA">The first string.</param>
        /// <param name="strB">The second string.</param>
        /// <param name="ignoreCase">if set to <c>true</c> the comparison is case insensitive.</param>
        /// <param name="alphanumericScore">The alphanumeric score.</param>
        /// <param name="specialCharScore">The special char score.</param>
        /// <param name="caseScore">The case score.</param>
        /// <returns>A <see cref="float"/> value that indicates the similarity between two strings,
        /// as shown in the following table.
        /// <list type="">
        /// <item>Zero : No similarity between <paramref name="strA"/> and <paramref name="strB"/>.</item>
        /// <item>One : <paramref name="strA"/> and <paramref name="strB"/> are similar.</item>
        /// <item>Between zero and one : <paramref name="strA"/> and <paramref name="strB"/> have some differences depending on case, special symbols etc.</item>
        /// </list>
        /// </returns>
        public static float Similarity(this string strA, string strB, bool ignoreCase,
                                       float alphanumericScore, float specialCharScore,
                                       float caseScore)
        {
            string strDummyA = strA;
            string strDummyB = strB;

            if (string.IsNullOrEmpty(strDummyA) && string.IsNullOrEmpty(strDummyB))
                return 1f;

            if (string.IsNullOrEmpty(strDummyA) || string.IsNullOrEmpty(strDummyB))
                return 0f;

            if (ignoreCase)
            {
                strDummyA = strDummyA.ToUpperInvariant();
                strDummyB = strDummyB.ToUpperInvariant();
            }

            int compareResult = strDummyA.CompareTo(strDummyB);
            if (compareResult == 0) return 1f;

            string littleString = strDummyA.Length > strDummyB.Length ? strDummyB : strDummyA;
            char[] littleArray = littleString.ToCharArray();
            string bigString = strDummyA.Length > strDummyB.Length ? strDummyA : strDummyB;
            char[] bigArray = bigString.ToCharArray();

            int diff = bigArray.Length - littleArray.Length;
            var scoreMatrix = new float[diff + 1];

            for (int i = 0; i < scoreMatrix.Length; i++)
            {
                scoreMatrix[i] = GetGreedyScore(bigString.ToCharArray(i, bigString.Length - i), littleArray,
                                                alphanumericScore, specialCharScore, caseScore);
                scoreMatrix[i] += i * specialCharScore;
            }

            float score = scoreMatrix.Max();
            return score / bigArray.Length;
        }

        private static float GetGreedyScore(IList<char> bigArray, IList<char> littleArray, float alphanumericScore,
                                            float specialCharScore, float caseScore)
        {
            int shortLength = littleArray.Count;
            int longLength = bigArray.Count;

            float score = 0f;
            for (int i = 0; i < shortLength; i++)
            {
                if (char.IsLetterOrDigit(bigArray[i]) && char.IsLetterOrDigit(littleArray[i]))
                {
                    if (bigArray[i].Equals(littleArray[i]))
                        score = score + alphanumericScore;
                    else if (char.ToUpperInvariant(bigArray[i]).Equals(char.ToUpperInvariant(littleArray[i])))
                        score = score + caseScore;
                }
                else if (!char.IsLetterOrDigit(bigArray[i]) && !char.IsLetterOrDigit(littleArray[i]))
                {
                    if (bigArray[i].Equals(littleArray[i]))
                        score = score + alphanumericScore;
                    else
                        score = score + specialCharScore;
                }
            }
            score += (longLength - shortLength) * specialCharScore;
            return score;
        }

        /// <summary>
        /// Returns a byte array which is equivalent to a specified string.
        /// </summary>
        /// <param name="strA">The string to convert to byte array.</param>
        /// <returns>A byte array representation of <paramref name="strA"/>.</returns>
        public static byte[] ToByteArray(this string strA)
        {
            return Encoding.Unicode.GetBytes(strA);
        }

        /// <summary>
        ///  Returns the ascii equivalent of a specified unicode string.
        /// </summary>
        /// <param name = "strA">The unicode string to convert.</param>
        /// <returns>ASCII representation of unicode <paramref name="strA"/>.</returns>
        public static string ToAscii(this string strA)
        {
            return Regex.Replace(strA, @"[^\u0000-\u007F]", "");
        }

        /// <summary>
        ///  Returns the unicode representation of a string.
        /// </summary>
        /// <param name="strA">The string to convert.</param>
        /// <returns>Unicode representation of <paramref name="strA"/>.</returns>
        public static string ToUnicode(this string strA)
        {
            using (var writer = new StringWriter())
            {
                foreach (char c in strA)
                {
                    char h1 = IntToHex((c >> 12) & '\x000f');
                    char h2 = IntToHex((c >> 8) & '\x000f');
                    char h3 = IntToHex((c >> 4) & '\x000f');
                    char h4 = IntToHex(c & '\x000f');

                    writer.Write('\\');
                    writer.Write('u');
                    writer.Write(h1);
                    writer.Write(h2);
                    writer.Write(h3);
                    writer.Write(h4);
                }

                string str = writer.ToString();
                return str;
            }
        }

        private static char IntToHex(int n)
        {
            if (n <= 9)
            {
                return (char) (n + 48);
            }
            return (char) ((n - 10) + 97);
        }

        /// <summary>
        ///   Determines whether the specified string contains white space.
        /// </summary>
        /// <param name="strA">The string to check for containing white space.</param>
        /// <returns>
        ///   <c>true</c> if the string contains white space; otherwise, <c>false</c>.
        /// </returns>
        public static bool ContainsWhiteSpace(this string strA)
        {
            return strA.Any(char.IsWhiteSpace);
        }

        /// <summary>
        /// Returns a new string by removing a specified <paramref name="character" /> from a string.
        /// </summary>
        /// <param name="strA">The original string.</param>
        /// <param name="character">The character to remove from the string.</param>
        /// <param name="position">The starting position of removal.</param>
        /// <remarks>
        ///   This operation is case sensitive.
        /// </remarks>
        /// <returns>A string obtained by removing the specified character from the specified position.</returns>
        public static string Remove(this string strA, char character, Position position)
        {
            return Remove(strA, character, position, false);
        }

        /// <summary>
        ///  Returns a new string by removing a specified <paramref name="character"/> from a string.
        /// </summary>
        /// <param name="strA">The original string.</param>
        /// <param name="character">The character to remove from the string.</param>
        /// <param name="position">The starting position of removal.</param>
        /// <param name="ignoreCase">if set to <c>true</c> the operation is case insensitive.</param>
        /// <returns>A string obtained by removing the specified character from the specified position.</returns>
        public static string Remove(this string strA, char character, Position position, bool ignoreCase)
        {
            char[] charArr = strA.ToCharArray();
            var targetArr = new List<char>();
            StringComparison options = ignoreCase
                                           ? StringComparison.InvariantCultureIgnoreCase
                                           : StringComparison.InvariantCulture;

            switch (position)
            {
                case Position.First:
                    int pos = strA.IndexOf(character.ToString(CultureInfo.InvariantCulture), options);
                    targetArr.AddRange(strA.Where((t, i) => i != pos));
                    break;
                case Position.Last:
                    int pos2 = strA.LastIndexOf(character.ToString(CultureInfo.InvariantCulture), options);
                    targetArr.AddRange(strA.Where((t, i) => i != pos2));
                    break;
                case Position.Any:
                    targetArr.AddRange(
                        charArr.Where(
                            element =>
                            !element.ToString(CultureInfo.InvariantCulture).Equals(
                                character.ToString(CultureInfo.InvariantCulture), options)));
                    break;
            }
            return new string(targetArr.ToArray());
        }

        /// <summary>
        /// Returns a new string by removing a specified sub-string from a string.
        /// </summary>
        /// <param name="strA">The original string.</param>
        /// <param name="subString">The sub-string to remove from The original string.</param>
        /// <returns>
        /// A string obtained by removing the sub-string from all position.
        /// </returns>
        public static string Remove(this string strA, string subString)
        {
            return Remove(strA, subString, Position.Any);
        }

        /// <summary>
        /// Returns a new string by removing a specified sub-string from a string.
        /// </summary>
        /// <param name="strA">The original string.</param>
        /// <param name="subString">The sub-string to remove from The original string.</param>
        /// <param name="position">The starting position of removal.</param>
        /// <remarks>
        ///   This operation is case sensitive.
        /// </remarks>
        /// <returns>
        /// A string obtained by removing the sub-string from all position.
        /// </returns>
        public static string Remove(this string strA, string subString, Position position)
        {
            return Remove(strA, subString, position, false);
        }


        /// <summary>
        /// Returns a new string by removing a specified sub-string from a string.
        /// </summary>
        /// <param name="strA">The original string.</param>
        /// <param name="subString">The sub-string to remove from The original string.</param>
        /// <param name="position">The starting position of removal.</param>
        /// <param name="ignoreCase">if set to <c>true</c> the operation is case insensitive.</param>
        /// <returns>A string obtained by removing the sub-string from the specified position.</returns>
        public static string Remove(this string strA, string subString, Position position, bool ignoreCase)
        {
            var targetArr = new List<char>();
            int end = subString.Length;
            StringComparison options = ignoreCase
                                           ? StringComparison.InvariantCultureIgnoreCase
                                           : StringComparison.InvariantCulture;

            switch (position)
            {
                case Position.First:
                    int start1 = strA.IndexOf(subString, options);
                    targetArr.AddRange(strA.Where((t, i) => (i < start1) || (i > start1 + end)));
                    return new string(targetArr.ToArray());
                case Position.Last:
                    int start2 = strA.LastIndexOf(subString, options);
                    targetArr.AddRange(strA.Where((t, i) => (i < start2) || (i > start2 + end)));
                    return new string(targetArr.ToArray());
                case Position.Any:
                    int[] indeces = strA.Occurrence(subString, ignoreCase).ToArray();
                    Array.Sort(indeces);
                    string str = strA;
                    int count = 0;
                    return indeces.Select(i => i - end * count++).Aggregate(str, (current, x) => current.Remove(x, end));
                default:
                    return strA;
            }
        }

        /// <summary>
        /// Returns a new string by replacing a specified string in the current
        /// instance with another specified string at a specified position.
        /// </summary>
        /// <param name="strA">The original string.</param>
        /// <param name="oldValue">The string to be replaced.</param>
        /// <param name="newValue">The string to replace the <paramref name="oldValue"/>.</param>
        /// <param name="position">The position at which replacing happens.</param>
        /// <returns>
        /// A string that is equivalent to the current string except that <paramref name="oldValue"/>
        /// are replaced with <paramref name="newValue"/>.
        /// </returns>
        public static string Replace(this string strA, string oldValue, string newValue, Position position)
        {
            switch (position)
            {
                case Position.First:
                    int pos = strA.IndexOf(oldValue);
                    strA = strA.Remove(pos, oldValue.Length);
                    strA = strA.Insert(pos, newValue);
                    return strA;
                case Position.Last:
                    pos = strA.LastIndexOf(oldValue);
                    strA = strA.Remove(pos, oldValue.Length);
                    strA = strA.Insert(pos, newValue);
                    return strA;
                case Position.Any:
                    return strA.Replace(oldValue, newValue);
                default:
                    return strA;
            }
        }

        /// <summary>
        ///   Returns the list of occurrences of a specified <paramref name="subString" /> string in a string.
        /// </summary>
        /// <param name="strA">The original string.</param>
        /// <param name="subString">The sub-string whose occurance to be found in the original string.</param>
        /// <remarks>
        ///   This operation is case sensitive.
        /// </remarks>
        /// <returns>A list of the start indices of the occurrence.</returns>
        public static IEnumerable<int> Occurrence(this string strA, string subString)
        {
            return Occurrence(strA, subString, false);
        }

        /// <summary>
        ///  Returns the list of occurrences of a specified <paramref name="subString" /> string in a string.
        /// </summary>
        /// <param name="strA">The original string.</param>
        /// <param name="subString">The sub-string whose occurance to be found in the original string.</param>
        /// <param name="ignoreCase">if set to <c>true</c> the operation is case insensitive.</param>
        /// <returns>A list of the start indices of the occurrence.</returns>
        public static IEnumerable<int> Occurrence(this string strA, string subString, bool ignoreCase)
        {
            RegexOptions options = ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None;
            var regex = new Regex(subString, options);
            MatchCollection matches = regex.Matches(strA);
            return from Match match in matches select match.Index;
        }

        /// <summary>
        ///   Removes all white space from a string.
        /// </summary>
        /// <param name="strA">The string to trim.</param>
        /// <returns>A new string in which there is no whitespace.</returns>
        public static string TrimAll(this string strA)
        {
            return new string(strA.Where(character => !char.IsWhiteSpace(character)).ToArray());
        }

        /// <summary>
        ///   Removes all characters specified in the <paramref name="trimChars" /> from a string.
        /// </summary>
        /// <param name = "strA">The string to trim.</param>
        /// <param name = "trimChars">The characters to remove from the string.</param>
        /// <returns>A new string in which there is no such characters specified in <paramref name="trimChars"/>.</returns>
        public static string TrimAll(this string strA, params char[] trimChars)
        {
            return new string(strA.Where(character => !trimChars.Contains(character)).ToArray());
        }

        /// <summary>
        /// Indicates whether a Unicode string is categorized as a decimal number.
        /// </summary>
        /// <param name="str">The string to check.</param>
        /// <returns>
        /// 	<c>true</c> if the string is a decimal number; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNumber(this string str)
        {
            return str.All(character => char.IsDigit(character) || character.Equals('.'));
        }

        /// <summary>
        /// Indicates whether a Unicode string is categorized as a number.
        /// </summary>
        /// <param name="str">The string to check.</param>
        /// <returns>
        /// 	<c>true</c> if the string is a number; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsAnyNumber(this string str)
        {
            return str.All(character => char.IsNumber(character) || character.Equals('.'));
        }

        #region | Code Taken from Mannex Library |

        /// <summary>
        ///   Encodes a string to its equivalent HTML encoded string.
        /// </summary>
        /// <param name = "strA">The string to cencode.</param>
        /// <returns>A new HTML encoded string.</returns>
        public static string HtmlEncode(this string strA)
        {
            strA.ThrowIfNull("strA");
            return HttpUtility.HtmlEncode(strA);
        }

        /// <summary>
        ///   Masks an empty string with a given mask such that the result
        ///   is never an empty string. If the input string is null or
        ///   empty then it is masked, otherwise the original is returned.
        /// </summary>
        /// <param name = "str">The original string.</param>
        /// <param name = "mask">The mask.</param>
        /// <returns>A new string after masking the original string.</returns>
        /// <remarks>
        ///   Use this method to guarantee that you never get an empty
        ///   string. Bear in mind, however, that if the mask itself is an
        ///   empty string then this method could yield an empty string!
        /// </remarks>
        public static string MaskEmpty(this string str, string mask)
        {
            return !string.IsNullOrEmpty(str) ? str : mask;
        }

        /// <summary>
        ///   Returns a section of a string from a give starting point on.
        /// </summary>
        /// <param name = "str">The original string.</param>
        /// <param name = "start">The slicing start position.</param>
        /// <returns>A new string obtained by slicing the original string.</returns>
        /// <remarks>
        ///   If <paramref name = "start" /> is negative, it is treated as
        ///   <c>length</c> + <paramref name = "start" /> where <c>length</c>
        ///   is the length of the string. If <paramref name = "start" />
        ///   is greater or equal to the length of the string then
        ///   no characters are copied to the new string.
        /// </remarks>
        public static string Slice(this string str, int start)
        {
            return Slice(str, start, null);
        }

        /// <summary>
        ///   Returns a section of a string.
        /// </summary>
        /// <param name = "strA">The original string.</param>
        /// <param name = "start">The start position of slicing.</param>
        /// <param name = "end">The end position of slicing.</param>
        /// <returns>A section of the original string.</returns>
        /// <remarks>
        ///   This method copies up to, but not including, the element
        ///   indicated by <paramref name = "end" />. If <paramref name = "start" />
        ///   is negative, it is  treated as <c>length</c> + <paramref name = "start" />
        ///   where <c>length</c> is the length of the string. If
        ///   <paramref name = "end" /> is negative, it is treated as <c>length</c> +
        ///   <paramref name = "end" /> where <c>length</c> is the length of the
        ///   string. If <paramref name = "end" /> occurs before <paramref name = "start" />,
        ///   no characters are copied to the new string.
        /// </remarks>
        public static string Slice(this string strA, int start, int? end)
        {
            strA.ThrowIfNull("strA");
            return SliceImpl(strA, start, end ?? strA.Length);
        }

        private static string SliceImpl(this string strA, int start, int end)
        {
            strA.ThrowIfNull("strA");
            int length = strA.Length;

            if (start < 0)
            {
                start = length + start;
                if (start < 0)
                    start = 0;
            }
            else
            {
                if (start > length)
                    start = length;
            }

            if (end < 0)
            {
                end = length + end;
                if (end < 0)
                    end = 0;
            }
            else
            {
                if (end > length)
                    end = length;
            }

            int sliceLength = end - start;

            return sliceLength > 0
                       ? strA.Substring(start, sliceLength)
                       : string.Empty;
        }

        /// <summary>
        ///   Embeds a string into <paramref name="target"/>, using {0}
        ///   within <paramref name="target"/> as the point of embedding.
        /// </summary>
        /// <param name = "strA">The string to embed.</param>
        /// <param name = "target">The target string where the string to be embedded.</param>
        /// <returns>A new string obtained after embedding a string into <paramref name="target"/>.</returns>
        public static string Embed(this string strA, string target)
        {
            strA.ThrowIfNull("strA");
            target.ThrowIfNull("target");
            return string.Format(target, strA);
        }

        /// <summary>
        ///   Wraps a string between two other string where the first
        ///   indicates the left side and the second indicates the
        ///   right side.
        /// </summary>
        /// <param name = "strA">The string to wrap.</param>
        /// <param name = "lhs">The left side string.</param>
        /// <param name = "rhs">The right side string.</param>
        /// <returns>A new string after wrapping a string by two specified strings.</returns>
        public static string Wrap(this string strA, string lhs, string rhs)
        {
            strA.ThrowIfNull("strA");
            return lhs + strA + rhs;
        }

        /// <summary>
        ///   Enquotes a string with <paramref name="quote"/>, escaping occurrences
        ///   of <paramref name="quote"/> itself with <paramref name="escape"/>.
        /// </summary>
        /// <param name="strA">The string to enquote.</param>
        /// <param name="quote">The quotation string.</param>
        /// <param name="escape">The escape string.</param>
        /// <returns>A new quoted string.</returns>
        public static string Quote(this string strA, string quote, string escape)
        {
            strA.ThrowIfNull("strA");
            StringBuilder sb = null;
            int start = 0;
            int index;
            while ((index = strA.IndexOf(quote, start)) >= 0)
            {
                if (sb == null)
                    sb = new StringBuilder(strA.Length + 10).Append(quote);
                sb.Append(strA, start, index - start);
                sb.Append(escape);
                start = index + quote.Length;
            }
            return sb != null
                       ? sb.Append(strA, start, strA.Length - start).Append(quote).ToString()
                       : strA.Wrap(quote, quote);
        }

        /// <summary>
        ///   Formats a string using <paramref name="args"/> as sources for
        ///   replacements and a function, <paramref name="binder"/>, that
        ///   determines how to bind and resolve replacement tokens.
        /// </summary>
        /// <param name = "format">The string to format.</param>
        /// <param name = "binder">The binder function.</param>
        /// <param name = "args">The formatting source.</param>
        /// <returns>A new formatted string.</returns>
        public static string FormatWith(this string format,
                                        Func<string, object[], IFormatProvider, string> binder, params object[] args)
        {
            return format.FormatWith(null, binder, args);
        }

        /// <summary>
        ///   Formats a string using <paramref name = "args" /> as sources for
        ///   replacements and a function, <paramref name = "binder" />, that
        ///   determines how to bind and resolve replacement tokens. In
        ///   addition, <paramref name = "provider" /> is used for cultural
        ///   formatting.
        /// </summary>
        /// <param name = "format">The string to format.</param>
        /// <param name = "provider">The format provider object.</param>
        /// <param name = "binder">The binder function.</param>
        /// <param name = "args">The formatting source.</param>
        /// <returns>A new formatted string.</returns>
        /// <exception cref="FormatException">Format does not meet the parameter specifications.</exception>
        public static string FormatWith(this string format,
                                        IFormatProvider provider, Func<string, object[], IFormatProvider, string> binder,
                                        params object[] args)
        {
            format.ThrowIfNull("format");
            binder.ThrowIfNull("binder");

            var result = new StringBuilder(format.Length * 2);
            var token = new StringBuilder();

            CharEnumerator e = format.GetEnumerator();
            while (e.MoveNext())
            {
                char ch = e.Current;
                switch (ch)
                {
                    case '{':
                        while (true)
                        {
                            if (!e.MoveNext())
                                throw new FormatException();

                            ch = e.Current;
                            if (ch == '}')
                            {
                                if (token.Length == 0)
                                    throw new FormatException();

                                result.Append(binder(token.ToString(), args, provider));
                                token.Length = 0;
                                break;
                            }
                            if (ch == '{')
                            {
                                result.Append(ch);
                                break;
                            }
                            token.Append(ch);
                        }
                        break;
                    case '}':
                        if (!e.MoveNext() || e.Current != '}')
                            throw new FormatException();
                        result.Append('}');
                        break;
                    default:
                        result.Append(ch);
                        break;
                }
            }
            return result.ToString();
        }

        #endregion
    }

    /// <summary>
    ///   Specifies the position in the string.
    /// </summary>
    public enum Position
    {
        /// <summary>
        ///   At the beginning of the string.
        /// </summary>
        First,
        /// <summary>
        ///   At the end of the string.
        /// </summary>
        Last,
        /// <summary>
        ///   Anywhere in the string.
        /// </summary>
        Any
    }
}